Skip to content

sync Preview.targetRotation with TransientSessionSettings.windowRotation - #656

Draft
Kimblebee wants to merge 5 commits into
kim/adaptive/layout-rotationfrom
kim/adaptive/viewfinder-rotation
Draft

Kimblebee wants to merge 5 commits into
kim/adaptive/layout-rotationfrom
kim/adaptive/viewfinder-rotation

Conversation

@Kimblebee

Copy link
Copy Markdown
Collaborator

Stacked PR: Based on kim/adaptive/layout-rotation

Summary

Plumbs the host OS windowRotation through TransientSessionSettings so Preview.targetRotation updates in place without rebinding the camera session whenever the window rotates (frozen while recording). Recent CameraX fixes (aosp/4408335, aosp/4408336, aosp/4408337) dynamically update ViewPort and StreamSharing crop/rotation when Preview.targetRotation changes on a bound use case, allowing windowRotation to live alongside deviceRotation in TransientSessionSettings. Sensor-aligned ViewPort aspect ratio and CameraState.isPreviewLandscape are coordinated with PR #592 (TODO(#592)).

Key Changes

  • In-Place TransientSessionSettings.windowRotation (CameraSessionSettings.kt, CameraSystem.kt, CameraXCameraSystem.kt, CameraSession.kt, ConcurrentCameraSession.kt, FakeCameraSystem.kt):
    • Add windowRotation: DeviceRotation = DeviceRotation.Natural to TransientSessionSettings and setWindowRotation to CameraSystem, updating transientSettings in place in CameraXCameraSystem just like setDisplayRotation.
    • Initialize Preview.Builder().setTargetRotation(windowRotation.toUiSurfaceRotation()) and call applyWindowRotation(newTransientSettings.windowRotation, useCaseGroup) in processTransientSettingEvents (and concurrent camera's transient settings collector) when windowRotation changes, citing aosp/4408335, aosp/4408336, aosp/4408337 and noting TODO(#592) on ViewPort construction.
  • Controller & Session Effect Recording Freeze (CameraController.kt, CameraControllerImpl.kt, FakeCameraController.kt, PreviewScreen.kt, CameraSessionEffect.kt):
    • Thread windowRotation = LocalWindowRotation.current from PreviewScreen through CameraSessionEffect -> CameraController.setWindowRotation.
    • Freeze windowRotation updates in pendingWindowRotation while isRecording == true so active recordings never shift preview orientation mid-clip, flushing any queued rotation once recording stops.

Technical Notes

  • TransientSessionSettings.windowRotation vs TransientSessionSettings.deviceRotation: Both signals live in TransientSessionSettings and update bound use cases in place without rebinding: deviceRotation updates imageCapture.targetRotation and videoCapture.targetRotation (for output file orientation metadata), while windowRotation updates preview.targetRotation (for live viewfinder buffer orientation, with aosp/4408335–4408337 dynamically updating ViewPort and StreamSharing).
  • Coordination with PR #592: PR #592 introduces getViewPortAspectRatio and CameraState.isPreviewLandscape. Keeping windowRotation focused on Preview.targetRotation and marking ViewPort construction with TODO(#592) avoids duplicate ViewPort helpers and ensures a clean merge when #592 lands.

Pre-Existing Bugs Fixed

  • Preview buffer misoriented when the window rotates (CameraSession.kt, ConcurrentCameraSession.kt): Preview.targetRotation previously never updated when the host window rotated in multi-window or large-screen postures. Updating Preview.targetRotation from TransientSessionSettings.windowRotation fixes this in place without tearing down the session.

Not Included Yet

  • Comes later in this series:
    • Upright content gravity & sticky 180° retention (kim/adaptive/content-gravity): Upright layout gravity for rectangular overlays (LandscapeTopEdgeBanner, Quick Settings landscape cross-fade and 380.dp scroll cap, CameraErrorDialog, ZoomBar labels, ToggleSwitch icons) and stateful sticky 180° gravity retention land in the next branch.
    • Wave 1 leaf component placeholders (inherited from kim/adaptive/layout-orchestrator): Side-rail postures retain placeholders for vertical TopBar indicators (#649), vertical CaptureModeToggle (#635), and QuickSettingsOverlayCardPlaceholder (#648), plus independent leaf polish (#622, #644).
  • Open follow-ups:
    • Sensor-aligned ViewPort aspect ratio and isPreviewLandscape (#592): Handled in PR #592, which pairs with Preview.targetRotation once both are merged into main.
    • Window rotation during a recording in split-screen: Window rotation updates are held while recording, but a split-screen host can still rotate the window mid-clip. How the viewfinder looks and behaves in that case has not been investigated yet.
    • Portrait Split-Screen Panes on Landscape Displays (W < H with windowRotation = 90°/270°): Once PR #592 lands (CameraState.isPreviewLandscape), PreviewDisplay can drive its aspect orientation from the oriented stream dimensions, falling back to windowRotation, so the uncropped frame letterboxes cleanly inside narrow split-screen panes.

Testing

  • ./gradlew :core:camera:testDebugUnitTest --tests "*CameraSessionTest*" — Initial Preview.targetRotation and in-place TransientSessionSettings.windowRotation updates across all four rotations without rebinding, while ImageCapture.targetRotation continues to track deviceRotation independently + FakeCameraSystem tracking.
  • ./gradlew :ui:controller:testing:testDebugUnitTest --tests "*FakeCameraControllerTest*" — setWindowRotation callback verification.
  • ./gradlew :ui:components:capture:testDebugUnitTest --tests "*CaptureScaffoldTest*" — CameraSessionEffect recording freeze and flush for both windowRotation and deviceRotation.

@Kimblebee Kimblebee changed the title Kim/adaptive/viewfinder rotation sync Preview.targetRotation with TransientSessionSettings.windowRotation Nov 13, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for tracking and applying host OS display/window rotation (windowRotation) to the camera sessions, ensuring that preview framing, viewports, and stream-sharing buffers match the active window. It adds setWindowRotation across CameraSystem, CameraController, and their implementations, and updates CameraSessionEffect to handle window rotation updates, including freezing them during recording. Additionally, unit tests were added to verify these behaviors. The review feedback suggests simplifying the calls to createUseCaseGroup in both CameraSession and ConcurrentCameraSession by omitting the redundant windowRotation argument, as it already defaults to the initial transient settings' window rotation.

Comment on lines +305 to +306
captureResults = captureResults,
windowRotation = currentTransientSettings.windowRotation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The windowRotation argument is redundant here because createUseCaseGroup already defaults windowRotation to initialTransientSettings.windowRotation (which is currentTransientSettings.windowRotation in this call). We can omit it to simplify the call.

                            captureResults = captureResults

Comment on lines 150 to +152
previewMirrorMode = sessionSettings.mirrorMode
.toCXConcurrentPreviewMirrorMode()
.toCXConcurrentPreviewMirrorMode(),
windowRotation = initialTransientSettings.windowRotation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The windowRotation argument is redundant here because createUseCaseGroup already defaults windowRotation to initialTransientSettings.windowRotation. We can omit it to simplify the call.

Suggested change
previewMirrorMode = sessionSettings.mirrorMode
.toCXConcurrentPreviewMirrorMode()
.toCXConcurrentPreviewMirrorMode(),
windowRotation = initialTransientSettings.windowRotation
previewMirrorMode = sessionSettings.mirrorMode.toCXConcurrentPreviewMirrorMode()

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for tracking and applying host OS display/window rotation independently of physical device rotation. It adds "windowRotation" to camera sessions, transient settings, and camera controllers, allowing the preview target rotation to track window rotation dynamically. Additionally, "CameraSessionEffect" is updated to freeze window rotation updates during recording, and comprehensive tests are added to verify these behaviors. The feedback suggests a minor improvement in "CameraSession.kt" to use "filterIsInstance()" for a more idiomatic Kotlin implementation when updating the preview target rotation.

Comment on lines +784 to +788
useCaseGroup.useCases.forEach { useCase ->
if (useCase is Preview) {
useCase.targetRotation = targetRotation
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using filterIsInstance<Preview>() is a more idiomatic and concise way to filter and iterate over specific use case types in Kotlin, aligning with the pattern already used elsewhere in this file (e.g., line 1047).

    useCaseGroup.useCases.filterIsInstance<Preview>().forEach { preview ->
        preview.targetRotation = targetRotation
    }
References
  1. Simplify Complex Logic: Look for needlessly complex code. If a multi-line block of logic can be condensed into a more concise and readable idiomatic expression (e.g., using Kotlin standard library functions), suggest the simplification. (link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant